home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Programmer Power Tools
/
Programmer Power Tools.iso
/
turbopas
/
sfmsrc.arc
/
SFMTECH.TXT
< prev
next >
Wrap
Text File
|
1987-06-28
|
62KB
|
1,629 lines
Super File Manager v1.0
PROGRAMMER'S GUIDE
David Steiner
2035 J Apt. 6
Lincoln, NE 68510
(402)475-0601
Written for:
Capitol PC User Group Inc.
1987 Software Programming Contest.
Permission is granted for Capitol PC and other not for
profit organizations to publish the source and executable
portions of this program.
>> OVERVIEW <<
This documentation file is not a user's manual. It is
designed to provide helpful information about the
techniques used in Super File Manager (SFM).
This document is not designed to be a DOS technical
reference either. I do not claim to be any kind of
authority on DOS or the other types of system calls used by
this program. It is entirely possible that information
presented here is not completely accurate. The only claim
I can make is that these routines work for me.
Before getting too far into this file I suggest that
you first take a brief look at the source code for SFM.
The style used may not be what your used to, but I like
it. In most cases the comments are kept to a simple
procedure description in order to avoid clutter.
Several items will be covered in this reference that I
think you will find useful:
Compiling and running SFM.
General design of a DOS call routine.
DOS function $32, non-documented but useful.
Using absolute disk reads and writes.
Trapping Turbo errors.
Designing an interrupt handler.
Trapping DOS critical errors.
An overall look at SFM.
Suggested references.
Acknowledgments.
This documentation file is being written fairly
hastily so I will apologize now if it doesn't flow terribly
well. If I had more time I would have had a friend or two
help work out the rough spots.
Super File Manager - 2 -
>> COMPILING SFM <<
Although the executable code (.COM file) distributed
with SFM will work on most any system, you will need
Borland's Turbo Pascal version 3.0 if you wish to customize
SFM to better suit your needs. This technical
documentation, however, presents ideas that may be
generalized to any other language. If you don't have Turbo
Pascal, but are fairly good at language conversions you
will also be able to make use of the source code.
It is a requirement stated in the contest rules that I
must describe all steps necessary to compile and run SFM.
I apologize to those who find these instructions
condescending.
To compile SFM you must first make sure that all of
the include files are present in the current directory.
These are:
SFM.PAS
SFMVARS.INC
SFMOTHER.INC
SFMSCRN.INC
SFMDOS.INC
SFMFUNC.INC
The next thing to do is start Turbo. Assuming this is done
and that the files above are in the current directory, here
is the command sequence for compiling to the file SFM.COM:
M ; Main file
SFM <ENTER> ; file name
O ; Options menu
C ; Compile to COM file
Q ; Quit options menu
C ; Compile
Q ; Quit Turbo
SFM <ENTER> ; Run SFM
The letters shown to the left are typed as shown, the ENTER
key need only be pressed when explicitly listed. SFM
should now be up and running, and it is time to refer to
the User's Guide if you aren't already familiar with SFM.
Super File Manager - 3 -
>> DOS CALLS <<
Making a DOS or BIOS call from Turbo Pascal is not all
that difficult. This section just gives an outline of a
good method for designing functions that make these calls,
as well as some specifics about how Turbo handles such
requests.
For those of you new to system calls a short
explanation is in order. There exists a vault of functions
available to you (as a programmer) that is always resident
in memory. Many of these functions are part of your
system's hardware (BIOS) as part of the ROM (read-only
memory). Many more become available when your system loads
DOS.
In most cases BIOS functions are very low level and
hard to work with. An example of the more useful BIOS
functions would be those for controlling the video
display. They allow you to alter the cursor, change the
video mode, read a character from the screen... the list is
a long one.
The DOS functions form something of a buffer between
you and the BIOS, but they are accessed similarly.
Naturally, the majority of DOS functions have to do with
disk access, but there are several other types also. IBM
suggests that you use the DOS functions whenever possible
since the BIOS may be different on later models of the PC
or even PC compatibles.
Calling these functions is accomplished at the
assembly language level through the use of the INT xx
(interrupt) instruction, where xx is an interrupt number.
The BIOS sets aside an area of memory for a table of
addresses when the system is turned on. Each entry in this
table is given a number, the interrupt number.
When you issue the INT instruction you are telling the
system to save its place and basically jump to the address
associated with that number. After the system routine has
finished it returns to where the program left off.
Before making such a call you must initialize the
registers so the interrupt will know exactly what to do and
how to find the input it requires. If you don't know what
a register is you should stop here. Find an introductory
text on assembly language and spend enough time with it to
get comfortable with the concept of registers before
continuing.
Super File Manager - 4 -
DOS CALLS : continued
How to initialize these registers varies greatly from
one function to the next, and a comprehensive list is
beyond the scope of this document. Please see the
references section below if you are looking for a good
place to get this information.
Now we will proceed with how to use this information
from Turbo Pascal. The Turbo procedures that we need to
know are the following two:
INTR( Interrupt : integer; var Regs : reg_T);
MSDOS( var Regs : reg_T);
They both require an input of type REG_T. Here is the best
way I know of to define this record:
reg_T = record case boolean of
true : (AX,BX,CX,DX,BP,SI,DI,DS,ES,Flags
: integer);
false : (AL,AH,BL,BH,CL,CH,DL,CH : byte);
end;
This is known as a variant record and allows us to use the
registers as full or half registers, just like we would
from assembly language.
The MSDOS procedure is really just a slight variation
of INTR. Most DOS functions are accessed through interrupt
number $21 and that is why there is a separate procedure
for them. I suspect that the MSDOS procedure really looks
something like this:
procedure MSDOS( Regs : reg_T );
begin
Intr( $21, Regs );
end;
Since this relationship has been pointed out I will only
talk about the INTR procedure for now. Although redundant,
the MSDOS procedure does help your code's clarity and
should be used when appropriate.
INTR acts in the following manner. First it saves the
current registers and then puts the values you specified in
the REGS record into the registers. It then issues an INT
instruction for the interrupt you specified. Upon return
Turbo puts the new register values into REGS and restores
the values it saved to the actual registers.
After a call to INTR the REGS record will contain the
values you would expect from the system call. I think that
it's time for an example.
Super File Manager - 5 -
DOS CALLS : continued
An example of a BIOS call:
The procedure below will ask BIOS what the current
video mode is. This example shows what is known as a
subfunction. We will be calling interrupt $10, named
the BIOS Video I/O interrupt.
This interrupt performs all kinds of video functions,
and we may tell it which one we want by specifying a
subfunction. The one we want is number $0F, called
Get the Current Video Mode. The standard for
requesting a subfunction is to be put the function's
number into register AH.
The only input is loading the subfunction number into
Regs.AH.
Output from this interrupt is contained in the the AX
and BX registers as follows:
AL = video mode number
$00 = black & white 40 columns X 25 rows
$01 = color 40 X 25
$02 = b&w 80 X 25
$03 = color 80 X 25
$07 = monochrome text, 80 X 25
$04-$06,
$08-$0F = different graphics modes
AH = Number of columns
BH = Active video page (0 if graphics mode)
procedure GetMode(var ModeNum : integer );
var
Regs : reg_T;
begin
with Regs do
begin
AH := $0F;
Intr( $10, Regs );
ModeNum := AL;
end;
end;
Since this procedure is just for getting the mode
number we don't bother saving the other register
values.
Super File Manager - 6 -
DOS CALLS : continued
A side note: Once you start playing around with interrupts
you may discover what lies behind some of Turbo's
procedures and functions. For example Turbo's TEXTMODE
function apparently calls BIOS interrupt $10, subfunction
$00 - Set Video Mode. The Turbo constants used
(bw40,bw80,c40,c80) for this call correspond to the values
returned in the above GetMode example. This implies that
you could call TEXTMODE with some other number and actually
set a graphics mode (e.g. $04 for medium-resolution CGA).
I must admit that I haven't tried it yet, but it seems
highly likely.
If you have looked at the SFM source code you may have
noted that whenever I used the INTR or MSDOS procedures the
function call was named in a comment to the side. This is
a good habit that you should adopt. I would have done it
in the above example but these lines aren't wide enough to
allow such comments.
For you folks already familiar with using these Turbo
procedures we're finally getting to something you can use.
Now we get into those functions that may not always be
successful. The most common type of functions where this
can happen are those accessing a disk, the domain of DOS.
When using the MSDOS procedure (or INTR with $21) many
of the functions will not always work. DOS uses several
methods for letting us know something went wrong.
Much of the time the carry flag will be set if there
was a problem executing one of the DOS functions. You can
check this bit (bit 0 of the Flags register) by ANDing the
flags register with $01 and comparing this to zero:
if ( (Flags AND $01) <> 0 ) then ERROR else OK;
If there was an error the registers will not contain the
information you were expecting. Instead DOS will have
placed an error code in the AX register. This error code
will correspond to a message explaining what went wrong.
Hopefully the reference you chose for DOS function calls
has a list of these messages.
Another method often used is setting the AL register
equal to $FF if there was an error. I'm just trying to
give you the general idea here, you will have to check how
errors are handled for each function that you use.
Super File Manager - 7 -
DOS CALLS : continued
Well here is the main idea I want to get across for
this section. When you design a routine to make a system
call that may not always be successful you should make it a
Turbo function. This way the function result can pass back
the error code or at least a boolean value for whether or
not it was successful.
Then any procedure that calls your function can decide
what to do with an error on its own. You may have it abort
the program, ignore the error, or (more likely) print an
error message and allow the program to continue.
When SFM uses this technique it is sometimes two or
three levels deep in function calls, but the error code is
just passed back on through until it gets to a routine that
is designed for handling output. Then the error message is
finally printed out. A pretty good example of this is in
the ChangePath procedure found at the top of SFMFUNC.INC.
Another sample function:
We'll write an example that tries to rename
"\SFMTECH.TXT" to "\TRASH\BULL.XXX". We need to use
the MSDOS procedure, subfunction $56 - Rename a File.
You may have noticed that the new name contains a
different path. We can do that with this DOS
function. If you look into the SFM source code you
will see that the move command uses this DOS function.
If you're just learning this system call stuff you're
probably wondering how we're going to fit those
strings into the registers. The answer is - we don't.
All DOS expects is the addresses of these two strings,
loaded into the DS:DX and ES:DI register pairs
respectively.
To set these addresses you need to know a little
about two more Turbo procedures: OFS and SEG. SEG
returns the "high" portion of the address of a
variable, or the segment. OFS returns the "low"
portion, or offset within that segment. That's not a
very complete description, but it gives you the
general idea.
There is one more thing you must note when passing
strings to DOS functions. DOS doesn't recognize
Turbo's string structure. It expects the first
character at the address to be the start of the file
name and that the string be terminated by a NUL ($00)
character. This is known as an ASCIIZ string.
Super File Manager - 8 -
RENAME EXAMPLE : continued
The error codes returned from DOS calls like this
often have their own small set of error messages.
Those codes returned by this call (in AX) are:
$02 : File not found
$03 : Path not found
$05 : Access denied
$11 : Tried to rename to a different drive
function RenameStuff : integer;
var
Regs : reg_T;
oldstr, newstr : string[80];
begin
oldstr := '\SFMTECH.TXT' + #00;
newstr := '\TRASH\BULL.XXX' + #00;
with Regs do
begin
AH := $56;
DS := seg( oldstr[1] );
DX := ofs( oldstr[1] );
ES := seg( newstr[1] );
DI := ofs( newstr[1] );
MsDos( Regs );
if (Flags AND $01) <> 0 then
RenameStuff := AX
else
RenameStuff := 0;
end;
end;
Note that the OFS procedure calls are asking for the
offset to the first character in the string
(xstr[1]). This index is not necessary for getting
the segment portion of the address, but is done anyway
for consistency.
Well that's about it for what I had to say about DOS
calls. We'll discuss them again a bit later in the section
for trapping serious DOS errors.
Super File Manager - 9 -
>> DOS FUNCTION $32 <<
If you already have a reference for MS-DOS calls
you've probably noticed that there are several subfunction
numbers that are "reserved for DOS." Ever wonder what
mystical operations those functions performed? Well we are
about to unravel one of those mysteries...
Glenn Roberts authored an excellent article in PC Tech
Journal that covers the $32 DOS function. I must admit
that SFM would be crippled, slower, and less reliable
without it. This function request will return an address
to an immensely valuable table of diskette parameters.
Before going into the actual description, I want to
make sure you know that this is a NON-DOCUMENTED function.
This means that it may or may not remain in future versions
of DOS, MicroSoft doesn't guarantee anything. From the
information in Roberts' article it is valid for DOS
versions 2.0 through 3.1 and I can verify that it is still
there in version 3.2.
$32 - Get Device Parameter Table
Input:
AH = $32 for requesting the subfunction
DL = The drive you want the table for
(A=1, B=2,...)
Output:
DS = Segment of parameter table
BX = Offset of table
Error:
AL = $FF if the drive was invalid
Super File Manager - 10 -
FUNCTION $32 : continued
Table Contents:
Byte(s) Contents
------- ---------------------
0 Assigned disk (A=0, B=1,...)
1 Same as above, except 0 for RAM disk
2-3 Bytes per cluster
4 Sectors per cluster minus 1
5 Number of heads minus 1
6-7 Reserved sectors (bootstrap)
8 Number of FAT copies
9-10 Maximum number of root directory entries
11-12 First usable sector
13-14 Total cluster count plus 1
15 Sectors per FAT
16-17 First root directory sector
18-21 Device driver address
22-23 Media descriptor
24-27 Chain to next disk table
28-29 *Cluster of current working directory
30-93 *64-byte current working directory
* = valid only for DOS 2.x
If you plan on implementing this table in a Turbo program
there is a record defined for it in the SFMVARS.INC file.
Those of you paying attention may have noticed the
above inconsistency for numbering the drives. DOS is
pretty poor on this count. Notice that when you call
function $32 you ask for a drive's table according to A=1,
B=2... When information is returned in the table the drives
are numbered A=0, B=1... Keep this in mind when using DOS
calls and be especially careful about specifying which
drive you want to write to.
When using this table remember that you are looking
directly into the table that DOS uses. Changing values in
this table may affect the way DOS accesses the disk, which
would not be good if it happened to want to do a write
operation.
The only reason I can think of for wanting to change
these parameters would be during a format function. SFM
has no such function, but I did have one started before it
became too difficult to implement properly. I found that
in some cases this table had to be altered in order to
format diskette tracks. I don't feel qualified to give any
further discussion on formatting (I had a good start
though, until I tried formatting one of the AT's high
density drives at work).
Super File Manager - 11 -
>> ABSOLUTE DISK ACCESSES <<
Now I shall go into the Turbo INLINE command a bit.
It seems that the best way to read from or write directly
to diskette sectors would be to use the corresponding DOS
interrupts. There is something of a problem here though.
For some unknown reason the DOS interrupts $25, Absolute
Disk Read and $26, Absolute Disk Write both leave a copy of
the Flags register on the stack after returning from the
interrupt.
DOS apparently does a no-no. It seems to use the
wrong type of return from an interrupt handler. Something
I didn't mention above is exactly how the INT operation
works. Well, it puts a copy of the Flags register on the
stack, followed by two words for the return address.
Whenever an interrupt handler is designed there is supposed
to be an IRET instruction for returning to the calling
program. The IRET, of course, loads the return address and
then restores the Flags register before making the jump
back.
DOS, however, seems to use the normal RET instruction
to return from interrupts $25 and $26. This means that the
Flags don't get popped unless the calling procedure does so
itself after the INT call.
From the assembly language level this is just an
annoying little quirk. In Turbo Pascal the quirk develops
into one of those things people buy Preparation H to
remedy. The INTR procedure is not designed to handle these
special cases and those Flags left sitting on the stack
wreak havoc with the system, often causing a lockup.
The only ways to fix this problem from within Turbo is
to write an external subroutine or resort to machine
language using the INLINE command. I prefer INLINE code
since it doesn't require that you keep track of a separate
binary file. This command is used in the following manner:
Inline( $xx/$xx ... /$xx );
That's right, I didn't mistype. You must enter the actual
machine codes (hex numbers) for Turbo to accept INLINE
input. The only break Turbo offers you is that you can use
variable names when you need to, so you don't have to know
their exact address (which isn't possible anyway).
Fortunately there are alternatives to being a machine
code wizard. There are programs available that take small
files of pseudo-assembly code and do all of the machine
code conversions for you. The one I use is listed below in
the references section.
Super File Manager - 12 -
ABSOLUTE DISK ACCESS : continued
There are still minor drawbacks even with these
utilities. Because such programs must interface with Turbo
they often use a variation of assembly language. This
means learning a new dialect even if you are already
familiar with assembly. On the brighter side, alterations
aren't too great and don't take long to master.
An example is not presented here since both interrupts
that have this problem are already written as part of SFM.
They are two separate procedures and can be found in the
SFMDOS.INC file. Their names are LoadSectors and
WriteSectors.
You may also find examples of INLINE code below when
we discuss interrupt handlers.
Super File Manager - 13 -
>> TRAPPING TURBO ERRORS <<
The method used to trap Turbo run-time errors is
really quite simple. There is a Turbo variable named
ERRORPTR that you can change to point to a procedure of
your own. If your error routine is called AbortOnError you
can issue this statement
ErrorPtr := ofs( AbortOnError );
near the beginning of your program and when an error occurs
Turbo jumps there instead of jumping to its normal error
handler, expecting you to handle the error and then exit
the program. You won't find this in the Turbo manual,
instead it was put into a READ.ME file on one of the
compiler diskettes. I hope I never meet any of the
manual's authors since I couldn't afford the lawsuit I'd
get after punching them in the nose.
The restrictions placed on the error handling
procedure are that it must have two integers as its input
and that it contain a HALT command since the program must
be terminated. The following procedure layout is correct:
procedure AbortOnError( ErrNum, Addr : integer );
begin
{ Your code goes here }
halt;
end;
Within this procedure you can do whatever you wish, except
"end" it normally. You can even call another procedure.
The input parameters contain the following:
Hi( ErrNum ) = The error type
$00 = user break
$01 = I/O error
$02 = run-time error
Lo( ErrNum ) = The error code
Addr = Where the program was when
the error occurred.
SFM contains just such an error routine. While it
isn't likely that it will be called, it is still there just
in case. The procedure is found towards the top of the
SFMSCRN.INC file (named AbortOnError).
The Addr value may be converted to hex and entered in
the Find run-time error option from the Turbo Editor. This
will allow Turbo to place you where the error occurred in
relation to the source code, and you can take it from
there.
Super File Manager - 14 -
TRAPPING TURBO ERRORS : continued
Note that the main reason that SFM has its own error
handler is because it must perform several operations to
return the system to normal before exiting. You see, SFM
uses custom interrupt handlers and these interrupts must be
restored to normal or DOS will have a fit. The next
section will describe these interrupt handlers.
As a final note there is one special situation you may
not have considered yet. If the program is terminated by a
heap/stack collision (error $FF) you will not be able to
call any subprograms from within your error handler. To
alleviate this problem you can provide your program with a
means of freeing up some heap space.
First you must declare a variable that can contain a
pointer address. The one used by SFM is:
HeapStart : ^integer;
It doesn't matter much what type of variable HeapStart
points to, but a pointer to an integer is a common method.
Near the beginning of your program you simply issue a MARK
procedure call:
Mark( HeapStart );
Now HeapStart points at a memory location on the heap.
Later in the AbortOnError procedure you just issue a
RELEASE call:
Release( HeapStart );
Once this is done there will be some free space in memory
again and you can call other procedures if you like. You
must remember that the call to RELEASE will clear out
everything put on the heap after the last MARK call. This
means that any dynamic variables you were using are now
gone.
In case you were wondering how the program's heap and
stack are related, here is an explanation. The heap and
stack are both dynamic in nature. This means that the
amount of memory used by either can grow or diminish during
the program execution. It then makes sense that they share
the same area of memory.
Super File Manager - 15 -
TRAPPING TURBO ERRORS : continued
Let's take a look at where Turbo puts a program in
memory when it is loaded. First Turbo loads in the
program's code segment, containing all of the procedures
and typed constants defined. Then it sets up the program's
data segment, which consists of the global variables. All
of the memory left over becomes a playground for the stack
and heap.
Now let's look closer at the heap/stack space. Turbo
decides that the heap should start right after the the data
segment. Whenever more space is required for the heap
Turbo designates another chunk of memory to it (NEW or
GETMEM procedure calls). The stack, on the other hand,
starts at the outer limit of the program's memory.
Whenever it needs more memory Turbo takes another chunk
from that end (procedure calls and their local variables).
When the program is finished with memory on the heap
or stack it can be returned to the system. This is
accomplished through DISPOSE or RELEASE procedures for the
heap space. All that is required to restore stack space is
the return from the procedure that allocated the memory.
Turbo keeps track of how far out the heap and stack
are extended by setting a pointer to their outermost memory
location. Should these pointers ever cross you will
receive a heap/stack collision error message ($FF). This
means that the heap and stack were trying to use the same
memory area for their data at the same time.
One likely cause for such a collision would be to ask
Turbo to give you a large chunk of memory using a GETMEM
call. This is the type of error possible with SFM.
Another, not so obvious cause, is when a procedure or
function sets up a large array in its local variable
declarations. A third example would be a recursive routine
that calls itself many times. Since Turbo must save quite
a bit of information each time a procedure calls itself you
may eventually run out of memory.
Super File Manager - 16 -
>> INTERRUPT HANDLERS <<
Here is another use for Inline code. You can design
your own interrupt handlers. This is how SFM traps all of
the DOS critical errors and avoids the normal DOS message
"Abort, Retry, or Ignore?" Writing such a routine is no
small task, but I will give you a general outline.
The INT24 function in the SFMOTHER.INC file is such a
routine and INT10 from SFMVARS.INC is another example. The
first routine takes over DOS interrupt $24, the Critical
Error Handler Address. The second takes over BIOS
interrupt $10, the Video I/O interrupt.
The INT24 routines were developed by Bela Lubkin and
published in an article in Programmer's Journal.
The first thing to look at when designing an interrupt
handler is how the INT and IRET assembly instructions
work. These are covered well enough above in the absolute
disk read/write section.
Next we must take Turbo into account. Turbo issues
the following few lines of code at the beginning of every
procedure or function:
PUSH BP
MOV BP, SP
PUSH SP
These instructions set the BP register to a location that
can be used to index the variables in the subroutine's
declaration. Your major concern with these lines should be
how to put things back in order before returning from your
interrupt handler:
MOV SP, BP ; code to exit
POP BP
IRET
This exit code must be inserted at the end of your
interrupt handler to successfully return to the calling
program. You can't let your interrupt routine exit
normally since the IRET instruction must be used. An
exception to this rule would be DOS interrupts $25 and $26
for reasons explained in that section.
Super File Manager - 17 -
INTERRUPT HANDLERS : continued
If you were wondering why there are two PUSH
instructions inserted by Turbo but we only POP once I have
the answer, though it did have me confused for a bit.
Loading the SP register into BP effectively saves stack
pointer right after the PUSH BP instruction. The line
"MOV SP, BP" restores this value and has the same effect
as popping SP (and any local variables that were declared)
from the stack. This means that you must not change the BP
register, unless you take special precautions.
In addition to this exit code you must save and
restore all registers that are used by your routines.
Exceptions to this are those registers that are used for
output from the interrupt. Before designing an interrupt
handler you should know exactly which registers these are
and what their output is supposed to look like.
That covers the basics of the interrupt handling
routine, now we must examine what is required to make the
system use that code. SFM's procedures for doing this are
called INT24ON and INT10ON for the indicated interrupt
handlers.
Recalling that the BIOS sets up a table in memory for
the addresses of the interrupt handlers (a vector table),
it seems obvious that all we need to do is change the
vector that corresponds to our interrupt. Rather than
attempting to locate and change this entry ourselves we
will let DOS do the dirty work for us.
DOS has a pair of subfunctions that will either get or
set these vectors for us. The function numbers are $35 for
getting the address and $25 for setting it. The input for
this function call is:
AH = $35 or $25 as needed
AL = interrupt to get/set
DS:DX = new address when setting
output:
ES:BX = current address when getting
Notice that the ES:BX register pair will contain the
address that the vector currently points to after function
$35 is called. There is no output for the $25 function,
but the vector will have been changed (even if the address
points to an invalid memory location).
Super File Manager - 18 -
INTERRUPT HANDLERS : continued
Before changing the interrupt handler you should save
the old address. This is done by setting aside a four byte
space to contain the old vector. That way the interrupt
may be restored before the program exits.
An example:
This interrupt handler will prevent the video screen
from being printed while the program is running. It
is being presented as an entire program so you can see
everything that is required.
program NoPrint;
type
Reg_T = record case boolean of
true : (AX,BX,CX,DX,BP,SI,DI,DS,ES,Flags : integer);
false : (AL,AH,BL,BH,CL,CH,DL,DH : byte);
end;
const
DataSeg : integer = 0;
OldInt05 : array[0..1] of integer = (0,0);
procedure Int05;
begin
{
PUSH BP ; Done by Turbo
MOV BP, SP
PUSH SP
}
Inline(
$50 { PUSH AX ; Save all regs }
/$53 { PUSH BX ; so we can use }
/$51 { PUSH CX ; Turbo code }
/$52 { PUSH DX }
/$57 { PUSH DI }
/$56 { PUSH SI }
/$06 { PUSH ES }
/$1E { PUSH DS }
{ ; }
{ Set DS so we can use global }
{ variables and the Turbo }
{ write procedures.} }
{ ; }
/$2E/$A1/>DATASEG {CS: MOV AX, [>DataSeg] }
/$8E/$D8 { MOV DS, AX }
);
writeln( 'Sorry, can''t print the screen right now.' );
write( #7 );
Super File Manager - 19 -
NOPRINT : continued
InLine(
$1F { POP DS ; Restore all regs}
/$07 { POP ES }
/$5E { POP SI }
/$5F { POP DI }
/$5A { POP DX }
/$59 { POP CX }
/$5B { POP BX }
/$58 { POP AX }
{ ; }
/$89/$EC { MOV SP, BP ; Exit code }
/$5D { POP BP }
/$CF { IRET }
);
end;
procedure Int05ON;
var
Regs : reg_T;
begin
DataSeg := Dseg;
with Regs do
begin
AH := $35; { DOS function $35 - Get Interrupt Vector Address }
AL := $05; { getting $05 - Print Screen }
MsDos( Regs );
OldInt05[1] := ES;
OldInt05[0] := BX;
AH := $25; { DOS function $25 - Set Interrupt Vector Address }
AL := $05; { setting $05 - Print Screen }
DS := Cseg;
DX := ofs( Int05 );
MsDos( Regs );
end;
end;
procedure Int05OFF;
var
Regs : reg_T;
begin
with Regs do
begin
AH := $25; { DOS function $25 - Set Interrupt Vector Address }
AL := $05; { setting $05 - Print Screen }
DS := OldInt05[1];
DX := OldInt05[0];
MsDos( Regs );
end;
end;
Super File Manager - 20 -
NOPRINT : continued
var
ch : char;
begin
DataSeg := Dseg;
Int05ON;
writeln( 'Hit the SPACE BAR to exit.' );
writeln;
repeat
read( kbd, ch );
until ch = #32;
Int05OFF;
end.
As you can see there are three main parts to setting up an
interrupt handler: the actual handler code, the procedure
to save the old vector and set yours, and finally the
procedure to restore the old vector. In addition to this
you must set aside a bit of space to store the old vector
and perhaps the data segment also.
In this example the data segment was saved in order to
allow us to use the Turbo write procedures. I did some
checking with DEBUG and it seems that Turbo keeps some
information used by the write procedure in the program's
data segment. Restoring the DS register also allows your
program to access the global variables.
Even if you don't fully understand what I have
presented here you can still used the above example as a
template to create your own interrupt handlers. All you
need to do is change all occurrences of "05" into whatever
interrupt you wish to customize.
Now I must clarify that last statement a bit. You
must be careful what interrupt vectors you are changing.
The majority of interrupt routines used by the BIOS or DOS
have several subfunctions. If you take control of such a
vector you will either have to emulate all of its functions
or be selective about which functions you are controlling.
The INT10 procedure found in SFMVARS.INC is one of
these cases. Emulating all of the subfunctions of the BIOS
Video Interrupt would take an incredible amount of code and
essentially be wasted space. For this reason I chose to
compare the value in the AH register and let the BIOS have
all function requests except the one that I wanted to
alter, the $0E function (Write Character as a Teletype).
Super File Manager - 21 -
INTERRUPT HANDLERS : continued
The transfer of control to the BIOS is accomplished by
making a FAR CALL to the old vector address we have taken
the precaution of saving. INT10 then issues an IRET right
away in order to return to the calling procedure.
I won't cover this in any more detail since the
example can be found in the SFM code if you wish to pursue
the matter.
Super File Manager - 22 -
>> TRAPPING DOS CRITICAL ERRORS <<
Now that you have some idea of how to design an
interrupt handler we can continue with a practical use.
The INT24 interrupt handler mentioned above has become one
of my most useful procedures and an addition to any program
of conseqence.
The DOS interrupt $24 is the one that prints that
nasty "Abort, Retry, Ignore?" message that has blemished so
many otherwise well designed display screens. By taking
control of this vector we may stop DOS from printing a
message and print our own error message at our leisure.
To make use of this routine from Turbo we must first
take note of a special compiler directive. This is the "I"
compiler directive used for turning Turbo's DOS error
checking on, {$I+}, or off, {$I-}. The default is on,
which allows DOS to print the error message mentioned
above.
When we turn this error checking off we assume
responsibility for DOS errors. If we don't do something
about the error before the next DOS function call the
program will abort whether or not Turbo was performing
error checking. This means that even if we intend to
ignore the error we must at least check the error code
using INT24RESULT (this also clears the error code).
Using this error checking with the standard Turbo
function IORESULT will catch most minor errors. Such
errors include messages like "File does not exist" or "File
not Open." It does not allow for more critical errors such
as an unformatted disk or an open diskette drive.
To use the IORESULT function you simply turn off the
Turbo error checking before an operation that accesses the
disk and then turn it back on right away. Then you may
access the IORESULT function to get an error code. If this
code is zero then no error occurred. Here is a small
example for changing the current directory:
{$I-}
chdir( newpath );
{$I+}
ErrCode := IOresult;
In this example NEWPATH is a string variable that contains
the path we are attempting to change to. ERRCODE is an
integer variable that will hold the code returned by
IORESULT. This example could become the body of a DOS call
function that follows the outline given in the first
section of this documentation.
Super File Manager - 23 -
DOS CRITICAL ERRORS : continued
Now we may extend this error checking to include
errors that are considered critical to DOS. If you
implement the INT24 set of procedures you may use
INT24RESULT in the same manner as you would normally use
IORESULT. The difference is that the new function result
will actually contain two error codes. There is one placed
in both the high and low bytes of the integer result. To
see how to extract these two codes take a look into the
ErrorMessage routine found in the SFMOTHER.INC file.
Keeping this in mind we can now take the final step in
designing a break-proof DOS call function that returns a
comprehensive error code. I think an example is the best
way to illustrate this. To avoid setting up an entirely
new example we will just enhance the renaming function from
the DOS calls section above:
function RenameStuff : integer;
var
Regs : reg_T;
ErrCode : integer;
oldstr, newstr : string[80];
begin
oldstr := '\SFMTECH.TXT' + #00;
newstr := '\TRASH\BULL.XXX' + #00;
with Regs do
begin
AH := $56;
DS := seg( oldstr[1] );
DX := ofs( oldstr[1] );
ES := seg( newstr[1] );
DI := ofs( newstr[1] );
{$I-}
MsDos( Regs );
{$I+}
ErrCode := Int24result;
if ErrCode <> 0 then
RenameStuff := ErrCode
else
begin
if (Flags AND $01) <> 0 then
RenameStuff := (AX SHL 8) OR $8000
else
RenameStuff := 0;
end;
end;
end;
Super File Manager - 24 -
Looking at this code may be a bit confusing, but it
covers all three levels of DOS error messages. The first
two are taken care of by the INT24RESULT function which
combines both Turbo and DOS error messages. The third type
of error is that mentioned in the DOS calls section. This
error code has been altered a bit in order to create an
integer function result that can be sent to the
ErrorMessage procedure.
The alteration is this: error codes that are returned
by the function call itself are shifted left so the occupy
the high byte along with the DOS critical error codes.
Then the high bit is set to let ErrorMessage know that this
error code is one returned from a DOS function call rather
than the DOS critical error handler.
Again, even if you don't fully understand this process
you can still take the procedures and place them into your
own program. The only place you will need to make changes
is in ErrorMessage, in order to make it fit your I/O
routines and also to restore the error messages that I
commented out. You may then use INT24RESULT just as you
would use the standard Turbo IORESULT.
Super File Manager - 25 -
>> SFM OVERVIEW <<
Now that we have covered those techniques that I
thought needed more background, we can cover Super File
Manager's structure in specific.
The first thing to note about SFM will be how it is
broken down into include files:
SFM.PAS Naturally this is the main file. It
contains the initialization routines
for setting up SFM. It also contains
the routines for the first level of
I/O, the command menus.
sfmVARS.inc This file contains all of the types
that are used by SFM as well as the
majority of global variables and
constants. Also included are a few
routines that needed to be near the
beginning, but didn't belong in the
sfmOTHER.inc file.
sfmOTHER.inc Here we have the two sets of routines
that I borrowed from other sources.
These are DISPLAY and the INT24
routines.
sfmSCRN.inc Here is the file for the majority of
low-level I/O routines and help
displays. These include everything
from setting the video mode on up to a
custom string input function.
sfmDOS.inc This file contains the low-level DOS
function calls that generally don't
perform much screen I/O. Most of these
functions return error codes as
described in previous sections.
sfmFUNC.inc This last include file contains the
routines that merge the SCRN, DOS and
SFM files. In other words, they are
called from SFM.PAS and use the
routines in sfmSCRN.inc to set up calls
to the routines in sfmDOS.inc (which do
all the actual work).
Of course there are procedures in each file that may belong
somewhere else, but in some cases this is not possible.
Super File Manager - 26 -
SFM : continued
Actually, that breakdown gives a pretty good feel for
how control is passed around within SFM. We always start
in SFM.PAS and the selection of a command usually sends us
off to sfmFUNC.inc. From there things jump around
according to the function.
As mentioned above, nearly all error checking is
returned through function results and then the codes are
sent off to the ErrorMessage routine. Exceptions to this
are warning messages or those errors specific to SFM (such
as "Windows must have different paths").
Within the sfmSCRN.inc file there are two error
routines that bear special explanations. These are the
AbortProgram and AbortOnError routines. Since we are using
custom interrupt handlers these must be turned off before
the program is exited, including if SFM happens to be
halted by an error.
For this reason all exits of the program are routed
through AbortProgram, with the exception of normal
termination. This way it is less likely that we will
forget to turn off an interrupt handler or to turn the
cursor back on. An example of an error SFM does not handle
is the case of a bad file allocation table. Rather than
take the chance of making things worse SFM will quit.
The AbortOnError routine sets up a special error
message that takes the place of the normal Turbo
termination message. It displays the same information in a
slightly different format and also gives us the chance to
call AbortProgram in order to return the system to normal
as explained above.
If you want to see AbortOnError in action just go into
the SFM source code and change the line containing "{$C-}"
to "{$C+}". Then run SFM normally and type Ctrl-Break.
You will then see the AbortOnError message screen.
The address displayed here can then be entered in the
Turbo "Find run-time error" option from the editor. This
allows you to find out where SFM was when you pressed the
Ctrl-Break key sequence.
Super File Manager - 27 -
COMPILER DIRECTIVES USED BY SFM
Super File Manager uses a few of the Turbo compiler
directives. Setting the "C-" option is done simply to
speed up screen displays and allow the use of the keyboard
buffer. The "I-" and "I+" compiler directives are
described above under the DOS critical errors section.
A less known compiler directive is "K-". This
directive tells Turbo to stop checking for a heap/stack
collision whenever a procedure or function is called.
While this is not always a good idea, it is done here
because it saves SFM about five kilobytes of code space.
We only take back about 500 bytes of this when we perform
our own stack checking where it is needed.
SFM performs stack checking mostly by making a call to
MemoryAvail to find the amount of free space before issuing
a GETMEM call. The function MemoryAvail makes a call to
the standard Turbo function MaxAvail and then uses this
value to calculate the number of free bytes. Before
returning this number, SFM subtracts the number of bytes
specified by the constant MinStack in order to provide a
safety margin for procedure calls.
THE COPY ROUTINES
The most frequently used procedure in SFM has to be
CopyEntries. This is the routine that performs the actual
copy operations. Therefore it deserves a bit of special
attention.
The first item of interest here is the BUFFER array.
This array is made up of a small record that contains
information about the file being copied. The address field
is a pointer to the start of the buffer. The next field
contains the index in the source directory that the buffer
belongs to. The size is simply how big this buffer is, as
an unsigned integer. The last field tells whether or not
the file was too large to fit into one buffer (files over
64K) and that there is more to follow.
Using this array of BUFFER records we may now begin a
copy operation. The first action is to open the file to
read from and then allocate enough memory for that file.
The OPENFILE procedure uses the newer DOS method for files,
known as file handles.
Super File Manager - 28 -
COPYING FILES : continued
If the file is too large for one buffer, or we run out
of heap space, the MORE flag is set. This is repeated
until we run out of memory, files, or buffers. Note that
if we stop reading from a file before all of it is loaded
the RHANDLE (read-handle) is not reset to zero. This is
our method for keeping track of whether or not part of a
file has already been copied when we start the next read
pass. If a file is completely loaded then we call
CLOSEFILE with RHANDLE, which also sets it to zero.
After we have read in as much as possible we must then
start writing back to the destination path. We keep track
of whether or not part of the file was already written by
using the same method as with the read-handle. If WHANDLE
is set to zero then we must use CREATEFILE to make the new
file. Note that this function will fail if the file name
already exists with the read-only attribute set, otherwise
it will overwrite an existing file of the same name. If
there is an error we allow the user the option of
continuing with the next marked file or trying again.
If the disk becomes full there are two possibilities.
If the disk is in a floppy drive we can allow the user to
continue by making a call to the ChangeCopyDisk procedure,
otherwise we must abort the function.
After a change of disks SFM will try to continue where
it left off. There are two cases where it will have to
back up a bit and reload files that were already read in
once before the disk change.
One case is when the clear disk option has been used.
The call to CLEARFAT caused SFM to set up a temporary disk
transfer area in the program's heap space. This transfer
area occupies the same memory area as the copy buffers and
will have overwritten the information there. Therefore we
must reload the files that were in the buffers. Note that
ChangeCopyDisk uses the SPLIT flag to indicate that a disk
has been cleared.
The second case offers insight into why I chose the
name for the SPLIT flag. This flag keeps an eye on the
following special case:
Super File Manager - 29 -
While we are reading files into memory we hit the
memory or buffer limit and the current file is split
between two buffers. Then after a successful write
pass we return to the read procedure and pick up
reading that file where we left off. Let's say that
on the next write pass we run out of diskette space
while writing that file. In this case the file is
deleted from the destination and we allow the user to
change disks. Now we are ready to continue, but we
don't have the entire file in memory anymore. We can
tell because the SPLIT flag is still true, so we must
return to the read pass and start over with that file.
That covers a not so obvious, but dangerous possibility.
As a final step in the WriteTo procedure we must close
the file handle and set the correct date and time. When we
close a file that was open for writing, DOS updates the
time and date to the current system values. To update
these fields we are required to reopen the file for
reading, use the appropriate DOS function for the update,
and then close the file again. The time and date we use is
retrieved from the entry stored in memory for the source
directory.
MENU TWO NOTES
When menu two is entered SFM loads a copy of the
directory into memory. All of the functions from this menu
will act on this copy and won't be updated on the disk
until the update disk function is used.
In addition to a copy of the current directory we also
keep a copy of the file allocation table. This FAT is only
used for recovering files when using the undelete
function. If the disk is updated after an undelete
operation then this FAT must also be written back to disk.
REARRANGING FILES
While in menu two the directory may be sorted or
rearranged using the pick up file function. Both methods
work only on the copy in memory and don't become permanent
until updated.
The Sort functions are fairly easy to understand.
Just for the record, the sort used is an insertion sort.
The major factors in choosing the type of sort used were
whether or not it was stable and how difficult it would be
to alter later.
Super File Manager - 30 -
REARRANGING FILES : continued
You may notice the array of real numbers that gets set
up for a couple of the sort fields. This allows us to set
up the keys for the directory once and thus avoid
recalculating the keys every time we need them. The time
for one such pass is nothing compared to the exponential
running time of the insertion sort.
As a final note on the sort routines, I'm not sure why
the character array comparisons work. I thought the
comparisons would have to be done on Turbo strings, but
they seem to work fine with arrays of characters. I
couldn't find anything in the Turbo reference manual to
either support or deny this.
Now, about the procedures for picking up and dropping
a directory entry. If you look at them you'll see that I
basically copied the routines that allow movement around
the directory windows and modified them to suit this
function. I don't like this duplication of code, but lack
the time to fix the problem. Other that that, the
procedures are fairly easy to understand.
SINGLE FLOPPY SYSTEMS
Support has been provided for systems having a single
floppy drive. Persons with such systems may occasionally
have use for a program such as SFM, but few programs
provide support for them. SFM does so by trapping the BIOS
interrupt 10 (Video I/O).
You may ask what this interrupt has to do with DOS and
the answer is - not much. However, DOS has a neat ability
to allow a user to use a single drive as two drives. When
DOS wants access to the disk that is supposed to be in the
other drive it asks the user to change disks.
This works fine from the DOS command prompt, but not
so well from within a program. DOS uses the Write
Character as Teletype BIOS Video function request to print
its message. When the bottom of the screen is reached this
function scrolls the entire screen, without paying
attention to any windows you may have specified. When this
happens the current display screen is ruined.
Super File Manager - 31 -
SINGLE FLOPPY SYSTEMS : continued
The INT10 procedure in SFMVARS.INC handles this in a
unique manner. It takes over the BIOS interrupt 10 and
when it is asked to write a character it uses the Turbo
WRITE procedure instead. This forces the message printed
to conform to the current window specified.
Granted, this is not the ideal way to handle the
problem, but I don't know the DOS interrupt that handles
the requests to change disks. For this reason, I had to
rely on a hunch that DOS used the specified BIOS interrupt
to write its message. What I ended up with is crude, but
it works just fine.
Super File Manager - 32 -
>> SUGGESTED REFERENCES <<
Here is a list of the books that I found useful in
designing SFM. The list isn't in a standard bibliography
format, but then this isn't a Technical Writing class
either.
Programmer's Reference Manual for IBM Personal Computers
Steven Armbrust & Ted Forgeron
1986: Dow Jones - Irwin
This was by far the most used of my references. It is
an excellent text that contains no-nonsense facts
about the majority of DOS and BIOS interrupts. In
many cases there are short example programs written in
Assembly Language, Pascal and C (examples in this
document are not plagiarized, they are original).
The Complete Guide to IBM PC AT Assembly Language
Harley Hahn
1987: Scott, Foresman and Company
Not used extensively for SFM, but is by far the best
Assembly Language book that I have seen. It serves as
both an introductory text and reference manual. You
don't have to own an AT to make use of it (my system
is an XT compatible).
The MS-DOS Handbook
Richard Allen King
1986: SYBEX, Inc.
This is a pretty good reference, but I don't use it
nearly as much as the first book listed above.
That about wraps up my favorite references. I have used
others, but not enough to bother listing.
Super File Manager - 33 -
>> ACKNOWLEDGMENTS <<
Here are some of the other sources that I found
useful.
Finding Disk Parameters
Glenn F. Roberts
May 1986: PC Tech Journal
Article covering the DOS function $32 request.
Dipping Into Directories
Ted Mirecki
February 1985: PC Tech Journal
Article on some ideas for loading subdirectories into
memory and altering them.
INLINE Interrupts
Charles C. Edwards
December 1985: PC Tech Journal
Article that contains some information on writing
interrupt handlers in Turbo Pascal. It clarifies a
few problems with the Turbo documentation.
INT24
Bela Lubkin
May/June 1986: Programmer's Journal
Article that contains the INT24 procedures used in
SFM.
DISPLAY.ARC
Keith G. Chuvala
File containing the DISPLAY procedure used in SFM. It
also includes a documentation file.
INLINE.ARC
L. David Baldwin
1985,86
File containing the inline assembler that I use and
another utility to turn inline code into its Assembly
Language equivalent. Documentation is also included.